Skip to content

Introduce move expressions (move($expr)) #155023

Open
TaKO8Ki wants to merge 15 commits intorust-lang:mainfrom
TaKO8Ki:move-expr-1
Open

Introduce move expressions (move($expr)) #155023
TaKO8Ki wants to merge 15 commits intorust-lang:mainfrom
TaKO8Ki:move-expr-1

Conversation

@TaKO8Ki
Copy link
Copy Markdown
Member

@TaKO8Ki TaKO8Ki commented Apr 9, 2026

View all comments

This is an experimental first version of move expressions.

This first version implements it just in plain closures. A support for coroutine closures will be added in follow up pull requests.

RFC: will be added later
Tracking issue: #155050
Project goal:

r? @nikomatsakis

@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Apr 9, 2026
@rustbot rustbot added T-clippy Relevant to the Clippy team. T-rustfmt Relevant to the rustfmt team, which will review and decide on the PR/issue. labels Apr 9, 2026
@rust-lang rust-lang deleted a comment from rust-log-analyzer Apr 9, 2026
@rust-lang rust-lang deleted a comment from rust-log-analyzer Apr 9, 2026
@rust-lang rust-lang deleted a comment from rust-log-analyzer Apr 9, 2026
@TaKO8Ki TaKO8Ki changed the title Move expressions (move($expr)) Introduce move expressions (move($expr)) Apr 9, 2026
@rust-log-analyzer

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

@TaKO8Ki TaKO8Ki marked this pull request as ready for review April 16, 2026 12:26
@rustbot rustbot added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Apr 16, 2026
@rustbot
Copy link
Copy Markdown
Collaborator

rustbot commented Apr 16, 2026

Some changes occurred in src/tools/clippy

cc @rust-lang/clippy

The parser was modified, potentially altering the grammar of (stable) Rust
which would be a breaking change.

cc @fmease

Some changes occurred in src/tools/rustfmt

cc @rust-lang/rustfmt

@rustbot rustbot removed the S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. label Apr 16, 2026
Comment thread compiler/rustc_ast_passes/src/feature_gate.rs Outdated
@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Apr 16, 2026
Comment thread tests/ui/feature-gates/feature-gate-move_expr.stderr
Comment thread compiler/rustc_ast_lowering/src/expr.rs
Comment thread compiler/rustc_ast_lowering/src/expr.rs
Comment thread compiler/rustc_ast_lowering/src/expr.rs
Comment thread compiler/rustc_ast_lowering/src/expr.rs
Comment thread compiler/rustc_hir/src/hir.rs Outdated
Comment thread tests/ui/move-expr/plain-closure.rs
Comment thread compiler/rustc_hir_typeck/src/upvar.rs
Comment thread compiler/rustc_hir_typeck/src/upvar.rs
Comment thread compiler/rustc_hir_typeck/src/upvar.rs Outdated
@rust-bors

This comment has been minimized.

@rustbot

This comment has been minimized.

@TaKO8Ki TaKO8Ki requested a review from nikomatsakis April 30, 2026 12:26
@rustbot rustbot added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Apr 30, 2026
@rustbot rustbot removed the S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. label Apr 30, 2026
@rust-log-analyzer

This comment has been minimized.

@rustbot
Copy link
Copy Markdown
Collaborator

rustbot commented Apr 30, 2026

This PR was rebased onto a different main commit. Here's a range-diff highlighting what actually changed.

Rebasing is a normal part of keeping PRs up to date, so no action is needed—this note is just to help reviewers.

@TaKO8Ki
Copy link
Copy Markdown
Member Author

TaKO8Ki commented Apr 30, 2026

View new commits


match closure.coroutine_kind {
// FIXME(TaKO8Ki): Support `move(expr)` in coroutine closures too.
// For the first step, we only support plain closures.
Copy link
Copy Markdown
Contributor

@nikomatsakis nikomatsakis Apr 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. I think it's fine to leave this for follow-up, but also I expect it will be quite straightforward to do.

View changes since the review

// selects the synthetic local for this exact `move(...)` occurrence.
if let Some((ident, binding)) = self
.move_expr_bindings
.last()
Copy link
Copy Markdown
Contributor

@nikomatsakis nikomatsakis Apr 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we push a new entry for every closure that we are lowering?

View changes since the review

// During body lowering, replace each `move(...)` occurrence with the
// synthetic local recorded in this closure's binding map. Nested closures
// push their own maps.
self.move_expr_bindings.push(bindings);
Copy link
Copy Markdown
Contributor

@nikomatsakis nikomatsakis Apr 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something about this feels "off" to me. I think this push should be part of lower_expr_closure rather than here. For one thing, I don't think that these move_expr_bindings should be in scope when we call lower_expr on line 1210, but for another, the call to move_expr_bindings.last()` implies to me that we always push something on the vector. I know that we always push things on the vector when it matters, but it still feels "odd".

Here is an interesting test case:

let v = "Hello, Ferris".to_string();

let r = || {
    || {
        move(move(v.clone())).len()
    }
};

println!("{v}, {}", r());

I don't exactly know what this will do. It depends on whether we visit the contents of move expressions in that collection visitor. I think what I would like it to do is to follow the desgaring. But for sure, with the code as it is right now, when we walk the outer closure "up-front" and we will fail to detect the move because it is contained in a nested closure.

I wonder about doing this a different way. What if we:

  • when we enter a closure body, we push an empty vector for "move expressions". When we enter other bodies (e.g., a top-level function), we push a placeholder like None.
  • when we encounter a move expression, we check if that vector is Some or None. None results in an error ("move" outside of closure). For Some, we push the expression (unlowered, I think?) into the vector and then we create a new variable and return it.

When we finish processing a closure (i.e., here) we pop that vector and walk over all the things that were collected, and lower the expressions. This should naturally "unfold" in the recursive case the way I expect.

Would this work? Or is there some complexity about the ordering when variables are created or something?

(Alternatively, we always push a vector, not an option, but then we error if, after a non-closure body, the vector is non-empty, but that seems a bit less robust to me (we have to remember to check everywhere).)

View changes since the review

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what I want to happen:

let v = "Hello, Ferris".to_string();

let r = || {
    || {
        move(move(v.clone())).len()
    }
};

println!("{v}, {}", r());

becomes

let v = "Hello, Ferris".to_string();

let r = || {
    || {
        move(move(v.clone())).len()
    }
};

println!("{v}, {}", r());

becomes

let v = "Hello, Ferris".to_string();

let tmp0 = v.clone();
let r = move(tmp0) || {
    let tmp1 = tmp0;
    move(tmp1) || {
        tmp1.len()
    }
};

println!("{v}, {}", r());

allow_async_fn_traits: Arc<[Symbol]>,

delayed_lints: Vec<DelayedLint>,
move_expr_bindings: Vec<NodeMap<(Ident, HirId)>>,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

under my proposed change, this would be Vec<Option<NodeMap<_>>>

@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Apr 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. T-clippy Relevant to the Clippy team. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-rustfmt Relevant to the rustfmt team, which will review and decide on the PR/issue.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants